#include "intensitytraffic.h"
#include "types.h"
#include <QSettings>
#include <QDebug>
#include <QTextCodec>
#include <QCheckBox>
#include <QGridLayout>
#include <QPushButton>
#include <QSpinBox>
#include <QGroupBox>
#include <QSpacerItem>
#include <QLabel>
#include <QHBoxLayout>

/*!
  Konstruktor ustawa początkowe wartości ileWjazdow i ileWyjazdow na 0, wywołuje funkcje countBranches() za pomocą której zczytuje ile i jakich jest gałęzi(wjazd/zjazd),
  a nnastępnie wywołuje funkcje populateMe(), która tworzy odpowiednie widgety w liczbie odpowiedniej do wcześniej zczytanych.
  */
IntensityTraffic::IntensityTraffic()
    : ileWjazdow(0), ileWyjazdow(0)
{
    QTextCodec::setCodecForTr (QTextCodec::codecForName ("UTF-8"));

    countBranches();
    populateMe();
}

/*!
  Tworzy layouty: _glownyLayout, _layoutGroup, _zatwierdzLayout;
  widgety: _wjazdyGroupBox, _natezenieWjazdowLabel, _wyjazdySpinBox, _wyjazdyLabel, _wjazdySpinBox,
  _zatwierdz; oraz listę _checkBoxExitList.
  Ustawia layouty i konfiguruje utworzone widgety.
  Łączy (connect):
  <ul>
    <li>przycisk _zatwierdz ze slotem checkProcent(), który sprawdza sumę natężeń wyjazdów na każdym wjeździe;
    <li>każdy SpinBox odpowiadający za natężęnie wjazdu/wyjazdu ze slotem paintTextButtonRedSlot();
    <li>każdy checkBox w widgecie ze slotem blockSpinBox();
  </ul>
  Na koniec odczytuje zapisane opcje funkcją readIntensitySettings() i jeszcze raz sprawdza sumę natężeń zjazdów
funkcją checkProcent().
*/
void IntensityTraffic::populateMe(QString name)
{
    //1. tworze odpowiednią liczbę QGroupBox,QSpinBox, itp
    _glownyLayout = new QGridLayout(this);
    _glownyLayout->setAlignment(Qt::AlignJustify);
    _wjazdyGroupBox = new QGroupBox*[ileWjazdow];
    _layoutGroup = new QGridLayout*[ileWjazdow];
    _natezenieWjazdowLabel = new QLabel*[ileWjazdow];
    _wyjazdySpinBox = new QSpinBox**[ileWjazdow];
    _wyjazdyLabel = new QLabel**[ileWjazdow];
    _wjazdySpinBox = new QSpinBox*[ileWjazdow];
    _zatwierdz = new QPushButton(tr("Zatwierdź zmiany"), this);
    _zatwierdzLayout = new QHBoxLayout();
    _checkBoxExitList = new QList< QList< QCheckBox* >* > ;

    connect(_zatwierdz, SIGNAL(clicked()), this, SLOT(checkProcent()));

    _zatwierdzLayout->addWidget(_zatwierdz, Qt::AlignTop);
    _glownyLayout->addLayout(_zatwierdzLayout, 0, 0, Qt::AlignTop);

    for (int i = 0; i < ileWjazdow; i++)
    {
        _wjazdyGroupBox[i] = new QGroupBox(tr("Wjazd ")+QString::number(double(numeryWjazdow.at(i))));
        _layoutGroup[i] = new QGridLayout;

        _natezenieWjazdowLabel[i] = new QLabel(tr("Udział: "));
        _wjazdySpinBox[i] = new QSpinBox(this);
        _wjazdySpinBox[i]->setSuffix("% ");
        _wjazdySpinBox[i]->setRange(0,100);

        connect(_wjazdySpinBox[i], SIGNAL(valueChanged(int)), this, SLOT(paintTextButtonRedSlot(int)));

        _wyjazdySpinBox[i] = new QSpinBox*[ileWyjazdow];
        _wyjazdyLabel[i] = new QLabel*[ileWyjazdow];
        _checkBoxExitList->append(new QList<QCheckBox*>);

        for (int j = 0; j < ileWyjazdow; j++)
        {
            _wyjazdySpinBox[i][j] = new QSpinBox(this);
            _wyjazdyLabel[i][j] = new QLabel((tr("Zjazd ")+QString::number(double(numeryWyjazdow.at(j)))));
            _wyjazdySpinBox[i][j]->setSuffix("% ");
            _wyjazdySpinBox[i][j]->setRange(0,100);
            (*_checkBoxExitList)[i]->append(new QCheckBox());

            _layoutGroup[i]->addWidget(_wyjazdyLabel[i][j], j+2 ,0);
            _layoutGroup[i]->addWidget(_wyjazdySpinBox[i][j], j+2 ,1);
            _layoutGroup[i]->addWidget((*_checkBoxExitList)[i]->at(j), j+2, 2);
            connect(_wyjazdySpinBox[i][j], SIGNAL(valueChanged(int)), this, SLOT(paintTextButtonRedSlot(int)));
            connect((*_checkBoxExitList)[i]->at(j), SIGNAL(toggled(bool)), this, SLOT(blockSpinBox(bool)));
        }

        _wjazdyGroupBox[i]->adjustSize();

        _layoutGroup[i]->addWidget(_natezenieWjazdowLabel[i], 0 ,0);
        _layoutGroup[i]->addWidget(_wjazdySpinBox[i], 0 ,1);
        _layoutGroup[i]->addItem(new QSpacerItem(0,10), 1 ,0);

        _wjazdyGroupBox[i]->setLayout(_layoutGroup[i]);
        _glownyLayout->addWidget(_wjazdyGroupBox[i]);
     }

    this->setLayout(_glownyLayout);

//    qDebug() << QObject::tr("PopulateMe almost done");
    //jeżeli sa ustawione opcje to je czytam
    readIntensitySettings(name);
//    qDebug() << QObject::tr("readIntensitySettings(); done");
    checkProcent(name); // on w środku zawiera wywołanie do saveIntensitySettings();
//    qDebug() << QObject::tr("checkProcent(); done");

    this->adjustSize();
//    qDebug() << QObject::tr("PopulateMe done");
}

/*!
  Funkcja wywoływana za każdym razem, gdy zostanie kliknięty dowolny checkBox z listy _checkBoxExitList.
  Wyszukuje wśród listy _checkBoxExitList, który z checkBoxów został kliknięty (spowodował wysłanie sygnału do slotu)
  i blokuje/odblokowuje odpowiadający mu SpinBox (blokowany spinBox zaznaczany jest na szaro i niemożliwa jest zmiana jego wartości.
  Zobacz \htmlonly <a href=" http://doc.trolltech.com/4.7/qwidget.html#enabled-prop">dokumentację Qt</a>) \endhtmlonly
  Parametr b jest wykorzystywany tylko do zapisania zmiennej prawda/fałsz przekazywanej przez sygnał toggled(bool).
  */
void IntensityTraffic::blockSpinBox(bool b)
{
    // trzeba znaleźć nadawcę;
    for (int i = 0; i < _checkBoxExitList->size(); i++)
    {
        QList< QCheckBox* >* lista = _checkBoxExitList->at(i);
        for (int j = 0; j < lista->size(); j++)
        {
            if (lista->at(j) != sender())
                continue;
            // znalazłem
            _wyjazdySpinBox[i][j]->setEnabled( !lista->at(j)->isChecked() );
        }
    }
}

/*!
Wywołuje trzy funkcje w następującej kolejności:
<ul>
<li>destroyLeftOptions() - niszczy dotychczsowe widgety
<li>countBranches() - zczytuje nowe informacje na temat gałęzi
<li>populateMe() - tworzy nowe widgety
</ul>
  */
void IntensityTraffic::changeLeftOptions(QString name)
{
    destroyLeftOptions();
    countBranches(name);
    populateMe(name);
}


/*!
  Zczytuje z opcji zapisanych na dysku liczbę gałęzi oraz informacje czy jest wjazdem, czy zjazdem i na tej podstawie
  zapisuje odpowiednie informacje do zmiennych ileWjazdow i ileWyjazdow.
  Więcej informacji na temat zczytywania z opcji zapisanych na dysku \link stronaZapisOpcji O zapisie opcji\endlink.
  */
void IntensityTraffic::countBranches(QString name)
{
    ileWjazdow = 0;
    ileWyjazdow = 0;
    numeryWjazdow.clear();
    numeryWyjazdow.clear();
    QSettings opcje("TabMar", name);
    int size = opcje.beginReadArray("Branch");
    for (int i = 0; i < size; ++i)
    {
        opcje.setArrayIndex(i);
        if (opcje.value("direction").toInt() == entry)
        {
            ileWjazdow++;
            numeryWjazdow.append(i);
        }
        else if (opcje.value("direction").toInt() == outlet)
        {
            ileWyjazdow++;
            numeryWyjazdow.append(i);
        }
    }
    opcje.endArray();
}

/*!
Usuwa wszystkie widgety: _wyjazdySpinBox, _wyjazdyLabel, _checkBoxExitList (czyści też listę),
_natezenieWjazdowLabel, _wjazdySpinBox, _wyjazdySpinBox, _wyjazdyLabel, _wjazdyGroupBox,
_wjazdySpinBox, _zatwierdz oraz layouty: _layoutGroup, _zatwierdzLayout, _glownyLayout
Funkcja wywoływana w changeLeftOptions().
  */
void IntensityTraffic::destroyLeftOptions()
{

    for (int i = 0; i <ileWjazdow; i++)
    {
        for (int j = 0; j <ileWyjazdow; j++)
        {
            delete _wyjazdySpinBox[i][j];
            delete _wyjazdyLabel[i][j];
        }

        QList< QCheckBox* >* list = _checkBoxExitList->at(i);
        foreach ( QCheckBox* checkBox,  *list)
        {
            delete checkBox;
        }

        delete _natezenieWjazdowLabel[i];
        delete _wjazdySpinBox[i];
        delete _wyjazdySpinBox[i];
        delete _wyjazdyLabel[i];
        delete _wjazdyGroupBox[i];
    }
    delete []_wjazdySpinBox;
    delete []_layoutGroup;
    delete _wyjazdySpinBox;
    delete _wyjazdyLabel;
    delete _wjazdyGroupBox;
    delete _zatwierdz;
    delete _zatwierdzLayout;
    delete _glownyLayout;
    _checkBoxExitList->clear();
}

/*!
  Wywoływana w funkcji checkProcent().
  Zapisuje wartości do QSettings poprzez wywołanie funkcji saveIntensitySettings() oraz zmienia
  kolor czcionki przycisku "zapisz zmiany" z powrotem na kolor czarny wywołując funkcję changeButtonTextColor().
  */
void IntensityTraffic::trafficChanged(QString name)
{
    saveIntensitySettings(name);
    changeButtonTextColor(_zatwierdz, Qt::black);
//    debug("trafficChanged() END");
}

/*!
  @param i - numer wjazdu, dla ktorego należy odznaczyć checkboxy.
  @return checkedOnes - lista wartości prawda/fałsz opisująca stany checkBoxów dla i-tego wjazdu przed ich odznaczeniem.

    Funkcja pomocnicza, wykorzystywana w funkcji checkProcent().
    Odznacza wszystkie checkBoxy z listy _checkBoxExitList dla i-tego wjazdu.
*/
QList <bool> IntensityTraffic::uncheckAll(int i)
{
    QList <bool> checkedOnes;
    QList< QCheckBox* >* lista = _checkBoxExitList->at(i);
    foreach ( QCheckBox* checkBox,  *lista)
    {
        checkedOnes.append(checkBox->isChecked());
        checkBox->setChecked(false);
    }
    return checkedOnes;
}

/*!
  @param i - numer wjazdu, dla ktorego ustawiamy checkboxy.
  @param checkedOnes - lista wartości prawda/fałsz opisująca stany checkBoxów dla i-tego wjazdu przed ich odznaczeniem. Zwrócona przez funkcję uncheckAll()

  Funkcja pomocnicza dla checkProcent().
  Dla i-tego wjazdu ustawia checkBoxy z _checkBoxExitList tak jak było przed wywołaniem funkcji uncheckAll().
  */
void IntensityTraffic::checkFromList(int i, QList<bool> checkedOnes)
{
    QList< QCheckBox* > *lista = _checkBoxExitList->at(i);
    if (checkedOnes.size() != lista->size())
    {
        qDebug() << "checkFromList ERROR size mismatch";
        return;
    }
    foreach ( QCheckBox* checkBox,  *lista)
    {
        checkBox->setChecked(checkedOnes.first());
        checkedOnes.removeFirst();
    }
}

/*! Funkcja sprawdza czy na każdym wjeździe suma natężeń wyjazdów wynosi 100%. W przypadku, gdy:
<ul>
<li>suma jest mniejsza od 100% wartości są równomiernie zwiększane w SpinBoxach tak aby suma była równa 100
<li>suma jest większa od 100% wartości są równomiernie zmniejszane w SpinBoxach tak aby suma była równa 100
</ul>
Oprócz tego, funkcja bierze pod uwagę checkBoxy. Jeżeli checkBox jest zaznaczony to jego wartość nie ulega zmianie (jeśli jest to możliwe).
W przypadku, gdy niemożliwe jest spełnienie obu warunków (suma natężeń wyjazdów równa 0 oraz brak zmiany wartości zablokowanych checkBoxów
wartości zablokowanych checkBoxów są zmieniane przy uzyciu fnkcji pomocniczych: uncheckAll(), checkFromList().
Najpierw zapamiętywane są stany checkBoxów, następnie odznaczane są wszystkie checkBoxy i wywołujemy funkcję checkProcent() (dla odznaczonych checkBoxów).
Po jej wykonaniu ponownie ustawiamy zapisane wcześniej stany checkBoxów.

Funkcja checkProcent() wywoływana jest za każdym razem gdy zmienia się wartość w dowolnym _wyjazdySpinBox (funkcja trafficChanged() ). */
void IntensityTraffic::checkProcent(QString name)
{
    if (ileWjazdow == 0)
        return;
    for (int i = 0; i < ileWjazdow; i++)
    {
        int suma = 0;
        int roznica = 0;
        short int reszta = 0;
        short int dodac = 0;

        //1. licze sume
        for (int j = 0; j < ileWyjazdow; j++)
            suma += _wyjazdySpinBox[i][j]->value();
        //jesli suma jst =100 to wszystko jest ok
        if (suma != 100)
        {
            int ileOdznaczonych = 0,  sumaOdznaczonych = 0;
            //2. zliczam liczbę odznaczonych
            QList< QCheckBox* > *lista = _checkBoxExitList->at(i);
            foreach ( QCheckBox* checkBox,  *lista)
                if (checkBox->isChecked() == false)
                    ileOdznaczonych++;

            for (int j = 0; j < ileWyjazdow; j++)
                if (_checkBoxExitList->at(i)->at(j)->isChecked() == false )
                    sumaOdznaczonych += _wyjazdySpinBox[i][j]->value();

            /* sumaOdznaczonych == 0 może oznaczać dwie rzeczy:
                                1) możemy dodać 100
                                2) nie ma żadnych odznaczonych. */

            QList <bool> checkedOnes;
            if (suma > 100)
            {
                int nadwyzka =  suma - 100;
                if ((nadwyzka > sumaOdznaczonych) || (ileOdznaczonych == 0)) // chcemy odjąć nadwyżkę od odznaczonych, ale suma odznaczonych jest zbyt mała
                {
                    checkedOnes = uncheckAll(i);
                    ileOdznaczonych = ileWyjazdow;
                    sumaOdznaczonych = suma;
                }
            }
            if (suma < 100)
            {
                int niedomiar =  100 - suma;
                if ((sumaOdznaczonych + niedomiar > 100) || (ileOdznaczonych == 0)) // nie zmieścimy niedomiaru w Odznaczonych, trzeba też dodać do zaznaczonych
                {
                    checkedOnes = uncheckAll(i);
                    ileOdznaczonych = ileWyjazdow;
                    sumaOdznaczonych = suma;
                }
            }
            // teraz trzeba wykonać zwykły Agusiowy podzial między odznaczone.
             // jeżeli na końcu checkedOnes.size() != 0, to odpalamy jeszcze checkFromList(i, checkedOnes);
            roznica = suma - 100;
            dodac = -1 * (roznica / ileOdznaczonych); // -1, żeby odejmować gdy suma > 100 i dodawać gdy suma < 100.
            reszta = -1 * (roznica % ileOdznaczonych );

            for (int j = 0; j < ileWyjazdow; j++)
            {
                if (_wyjazdySpinBox[i][j]->isEnabled())
                {
                    if ((_wyjazdySpinBox[i][j]->value() == 0) && (dodac + reszta < 0))
                        continue;
                    _wyjazdySpinBox[i][j]->setValue(_wyjazdySpinBox[i][j]->value() + dodac + reszta); //dodaje do kazdego SpinBoxa wartosc, zeby suma byla 100;
                    reszta = 0; // resztę dodajemy tylko do pierwszego z brzegu.
                }
            }
            if (checkedOnes.size() != 0)
                checkFromList(i, checkedOnes);
        }
        suma = 0;
        for (int j = 0; j < ileWyjazdow; j++)
            suma += _wyjazdySpinBox[i][j]->value();
        if (suma != 100) // jeśli chcieliśmy wartość ustawić gdzieś na (>100) lub (<0), to mogła zostać ona obcięta; dlatego wywołuję się jeszcze raz.
            checkProcent();
    }

    // and now we will do the same for Branch-intensities. Oh joy!
    int suma = 0;
    for (int i = 0; i < ileWjazdow; i++)
        suma += _wjazdySpinBox[i]->value();
    if (suma == 100)
        return;
    int roznica = suma - 100;
    int dodac = -1 * (roznica / ileWjazdow); // -1, żeby odejmować gdy suma > 100 i dodawać gdy suma < 100.
    int reszta = -1 * (roznica % ileWjazdow );
    for (int i = 0; i < ileWjazdow; i++)
    {
        if ((_wjazdySpinBox[i]->value() == 0) && (dodac + reszta < 0))
            continue;
         _wjazdySpinBox[i]->setValue(_wjazdySpinBox[i]->value() + dodac + reszta); //dodaje do kazdego SpinBoxa wartosc, zeby suma byla 100;
         reszta = 0; // resztę dodajemy tylko do pierwszego z brzegu.
    }

    suma = 0;
    for (int i = 0; i < ileWjazdow; i++)
        suma += _wjazdySpinBox[i]->value();
    if (suma != 100)
        checkProcent(name);
//    qDebug() << "checkProcent: Invoking trafficChanged();";
    trafficChanged(name);
 }

/*!
  Funkcja czyści dotychczasowe zapisane opcje na dysku(dla klucza "entryTraffic" i "outletTraffic") a następnie zapisuje nowe.
  (zobacz \link stronaZapisOpcji zapis opcji na dysk\endlink )

  Na koniec emituje sygnał newTrafficSettings() informujący o zapisaniu zmienionych opcji dotyczących natężenia samochodów.
  Sygnał ten jest przechwytywany przez funkcję RSim::readTrafficSettings().
  */
void IntensityTraffic::saveIntensitySettings(QString name)
{
//    qDebug() << "saveIntensitySettings: START";
    QSettings opcje("TabMar", "RoundaboutSimulator");//C:\settings

    countBranches(name);
    //wyczyszczenie zapisanych opcji (odpowiadających tym grupom)
    removeGroupSettings("entryTraffic");
    removeGroupSettings("outletTraffic");

    opcje.beginWriteArray("entryTraffic");
        for (int i = 0; i < ileWjazdow; ++i)
        {
            opcje.setArrayIndex(i);
            opcje.setValue("entryIntensity", _wjazdySpinBox[i]->value());
            opcje.setValue("entryNumber", numeryWjazdow.at(i) );
        }
   opcje.endArray();

//   qDebug() << "saveIntensitySettings: saving entryTraffic done";

   opcje.beginGroup("outletTraffic");
   for (int i = 0; i < ileWjazdow; ++i)
   {
       opcje.setValue("size", ileWjazdow);
       opcje.beginWriteArray("entry " + QString::number(double(i)));
       for (int j = 0; j < ileWyjazdow; j++)
       {
           opcje.setArrayIndex(j);
           opcje.setValue("outletIntensity", _wyjazdySpinBox[i][j]->value() );
           opcje.setValue("outletNumber", numeryWyjazdow.at(j)); // było i; wstawiłem j; MP88
       }
       opcje.endArray();
    }
   opcje.endGroup();

//   qDebug() << "saveIntensitySettings: saving outletTraffic done";
   emit(newTrafficSettings());

}

/*!
  Wywołuje funkcję countBranches(), żeby uaktualnić wartości dotyczące liczby wjazdów i wyjazdów (zmienne ileWjazdow i ileWyjazdow).
  Następnie odczytuje opcje uprzednio zapisane przy pomocy QSettings (zobacz \link stronaZapisOpcji zapis/odczyt opcji \endlink )
  i ustawia odpowiednie wartości. Jeżeli nie ma zapisanych opcji to ustawia wartości domyślne.
  */
void IntensityTraffic::readIntensitySettings(QString name)
{
    QSettings opcje("TabMar", name);

    countBranches(name);

    int size = opcje.beginReadArray("entryTraffic");
    for (int i = 0; ((i < size) && (i < ileWjazdow)); ++i) //ilosc entry w opcjach powinna być taka sama jak już utworzonych
    {
        opcje.setArrayIndex(i);
        _wjazdySpinBox[i]->setValue(opcje.value("entryIntensity", 0).toInt());
    }
    opcje.endArray();

    opcje.beginGroup("outletTraffic");
    for (int j = 0; j < ileWjazdow; ++j)//ilość wjazdów
    {
            opcje.beginReadArray("entry "+QString::number(double(j)));
            for (int k = 0; k < ileWyjazdow; k++)
            {
                opcje.setArrayIndex(k);
                _wyjazdySpinBox[j][k]->setValue(opcje.value("outletIntensity", 0).toInt());

            }
            opcje.endArray();
    }
    opcje.endGroup();
}

/*!
  @return _zatwierdz - zwraca adres do przycisku "zapisz zmiany".
  */
QPushButton *IntensityTraffic::getCmdApply()
{
    return _zatwierdz;
}

/*!
  Zmienia kolor czcionki znajdującej się na przycisku _zatwierdz na czerwony.
  Parametr jest nieużywany (potrzebny ze względu na dopasowanie sygnału do slotu).
  */
void IntensityTraffic::paintTextButtonRedSlot(int atrapa)
{
    changeButtonTextColor(_zatwierdz, Qt::red);
}


